package cc.eddic.pratice.programmingparadigms.streampractice.impl;

import cc.eddic.pratice.programmingparadigms.exam.QuestionType;
import cc.eddic.pratice.programmingparadigms.exam.domain.Answer;
import cc.eddic.pratice.programmingparadigms.exam.domain.Option;
import cc.eddic.pratice.programmingparadigms.exam.domain.Question;
import cc.eddic.pratice.programmingparadigms.streampractice.QuestionAccuracy;
import cc.eddic.pratice.programmingparadigms.streampractice.QuestionAndAnswer;
import cc.eddic.pratice.programmingparadigms.streampractice.ScoreOfStudentAndCategory;
import cc.eddic.pratice.programmingparadigms.streampractice.StreamPractice;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class StreamPracticeGcsImpl implements StreamPractice {
    @Override
    public List<QuestionAndAnswer> mapToQuestionAndAnswer(List<Question> questionList) {
        return null;
    }

    @Override
    public List<QuestionAccuracy> calculateAccuracy(List<Answer> answerList) {
        final List<InnerModel> answerModelList = getInnerModels(answerList);

        long studentCount = answerModelList.stream().map(InnerModel::getStudentName).distinct().count();
        if (log.isDebugEnabled()) log.debug("studentCount: {}", studentCount);

        val questionList = answerList.stream().map(Answer::getQuestion).distinct().collect(Collectors.toList());
        val defaultQuestion = new Question(0, "", QuestionType.SINGLE_CHOICE);

        return answerModelList.stream()
                .filter(InnerModel::isCorrect)
                .collect(Collectors.groupingBy(InnerModel::getQuestionNo, Collectors.counting()))
                .entrySet().stream()
                .map(entry -> new QuestionAccuracy(entry.getKey(),
                        questionList.stream().filter(q -> q.getNo() == entry.getKey()).findFirst().orElse(defaultQuestion).getDescription(),
                        entry.getValue().intValue() * 1d / studentCount))
                .sorted((Comparator.comparingDouble(QuestionAccuracy::getAccuracy)))
                .collect(Collectors.toList());
    }

    private static List<InnerModel> getInnerModels(List<Answer> answerList) {
        val defaultOption = new Option("", '*', true);
        return answerList.stream()
                .map(answer -> new InnerModel(answer.getStudent().getName(),
                        answer.getQuestion().getNo(),
                        answer.getQuestion().getDescription(),
                        answer.getQuestion().getSubCategory().getName(),
                        answer.getQuestion().getSubCategory().getCategory().getName(),
                        answer.getChoice(),
                        answer.getQuestion().getOptions().stream()
                                .filter(Option::isCorrectAnswer)
                                .findFirst()
                                .orElse(defaultOption)
                                .getChoice()))
                .collect(Collectors.toList());
    }

    @Override
    public List<ScoreOfStudentAndCategory> calculateScoreOfStudentAndSubCategory(List<Answer> answerList) {
        final List<InnerModel> answerModelList = getInnerModels(answerList);

        return answerModelList.stream().filter(InnerModel::isCorrect)
                .collect(Collectors.groupingBy(InnerModel::getStudentName,
                        Collectors.groupingBy(InnerModel::getQuestionSubCategory,
                                Collectors.counting())))

                .entrySet().stream()
                .flatMap(stu -> stu.getValue().entrySet().stream()
                        .map(category -> new ScoreOfStudentAndCategory(stu.getKey(), category.getKey(),
                                category.getValue().intValue() * 4)))

                .collect(Collectors.toList());
    }

    @Getter
    private static class InnerModel {
        public InnerModel(String studentName, int questionNo, String questionDescription, String questionSubCategory, String questionCategory, char choice, char correctAnswer) {
            this.studentName = studentName;
            this.questionNo = questionNo;
            this.questionDescription = questionDescription;
            this.questionSubCategory = questionSubCategory;
            this.questionCategory = questionCategory;
            this.choice = choice;
            this.correctAnswer = correctAnswer;
        }

        public boolean isCorrect() {
            return choice == correctAnswer;
        }

        String studentName;
        int questionNo;
        String questionDescription;
        String questionSubCategory;
        String questionCategory;
        char choice;
        char correctAnswer;
    }

}
